Skip to content

feat: Harness Phase 01 — search, git tools, and bash TUI approval#13

Merged
moyunzero merged 2 commits into
masterfrom
gsd/phase-1-search-git-foundation
Jun 27, 2026
Merged

feat: Harness Phase 01 — search, git tools, and bash TUI approval#13
moyunzero merged 2 commits into
masterfrom
gsd/phase-1-search-git-foundation

Conversation

@moyunzero

@moyunzero moyunzero commented Jun 27, 2026

Copy link
Copy Markdown
Owner

Summary

  • HARNESS-01: Swap grep implementation to @vscode/ripgrep (native .gitignore, --no-require-git for non-git dirs) while keeping tool name and output shape unchanged.
  • HARNESS-02: Add read-only gitStatus / gitDiff tools via simple-git in PLAN and BUILD modes; prefer over bash git * in system prompt.
  • HARNESS-03: Build-mode bash blocklist approval (Approve once / Reject / Allow for session) with keyboard navigation (default Reject), session allowlist, and three-layer permission enforcement (system prompt Rules 8–11, CLI gate, rich reject output-error).
  • Add docs/agent-permissions.md, bash transcript dual-line display (command + dim description), streaming flag fix for last assistant message, and 42 regression/integration tests.

Test plan

  • bun test packages/server/src/system-prompt.test.ts packages/cli/src/lib/bash-approval.test.ts packages/cli/src/lib/dialog-action-nav.test.ts packages/cli/src/lib/ripgrep.test.ts packages/cli/src/lib/git-tools.test.ts packages/cli/src/lib/local-tools.test.ts packages/shared/src/schemas.test.ts — 42 pass
  • Build mode: trigger rm -rf ./tmp → TUI dialog appears; Reject → model does not ask for typed chat confirmation
  • Build mode: ↑↓/Enter keyboard navigation on approval dialog; Esc dismisses as Reject
  • PLAN mode: gitStatus works; bash throws guard error
  • grep respects .gitignore (no node_modules matches)

Notes

  • Local dev notes at doc/harness-phase-01-search-git-notes.md are not in this PR (doc/ is gitignored on master).

Made with Cursor

Summary by CodeRabbit

  • New Features

    • Added guided approval for risky terminal commands, including a session-wide “allow” option and clearer command details in the chat UI.
    • Introduced new read-only git status and diff tools for safer repository inspection.
    • Improved search results to use a faster, more reliable search engine with better filtering.
  • Bug Fixes

    • Added safer handling for streaming assistant messages and tool errors.
    • Fixed dialog behavior so close actions are handled consistently before the dialog disappears.
  • Documentation

    • Added guidance on build-mode vs plan-mode permissions and available tools.

…hase 01)

Deliver HARNESS-01/02/03 with ripgrep-backed grep, gitStatus/gitDiff read-only tools, and a blocklist-driven bash approval dialog in Build mode aligned with Claude Code permission separation (TUI-only gate, no chat re-confirm after reject).

Co-authored-by: Cursor <cursoragent@cursor.com>
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@railway-app railway-app Bot temporarily deployed to lavish-courage / MoCode-TUI-pr-13 June 27, 2026 03:07 Destroyed
@coderabbitai

coderabbitai Bot commented Jun 27, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@moyunzero, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 43 minutes and 21 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits.

🚦 How do rate limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: cab18d3b-a941-47c8-abbb-8eb8329ab247

📥 Commits

Reviewing files that changed from the base of the PR and between 8f7635a and 214b615.

📒 Files selected for processing (10)
  • docs/agent-permissions.md
  • packages/cli/src/hooks/use-chat.ts
  • packages/cli/src/lib/bash-approval.test.ts
  • packages/cli/src/lib/bash-approval.ts
  • packages/cli/src/lib/local-tools.ts
  • packages/cli/src/lib/ripgrep.test.ts
  • packages/cli/src/lib/ripgrep.ts
  • packages/cli/src/providers/dialog/index.tsx
  • packages/shared/src/schemas.test.ts
  • packages/shared/src/schemas.ts
📝 Walkthrough

Walkthrough

This PR adds bash approval handling in Build mode, new dialog and chat wiring for approve/reject/session actions, ripgrep- and git-backed local tools, updated system prompts, and supporting docs/tests.

Changes

Build-mode approvals and local tools

Layer / File(s) Summary
Shared tool schemas and approval rules
packages/shared/src/schemas.ts, packages/shared/src/schemas.test.ts, packages/cli/src/lib/bash-approval.ts, packages/cli/src/lib/bash-approval.test.ts, packages/cli/src/lib/dialog-action-nav.ts, packages/cli/src/lib/dialog-action-nav.test.ts
gitStatus and gitDiff are added to shared tool schemas and contracts, and bash approval helpers plus dialog selection utilities add blocklist, allowlist, and movement logic with tests.
Dialog lifecycle and approval modal
packages/cli/src/providers/dialog/types.ts, packages/cli/src/providers/dialog/index.tsx, packages/cli/src/components/dialogs/bash-approval-dialog.tsx, packages/cli/src/components/dialogs/index.tsx, packages/cli/src/lib/bash-approval-ui.ts
Dialog close callbacks now run during teardown, and the bash approval dialog plus its helper resolve approve, reject, and allow-session outcomes.
Chat approval gate and assistant rendering
packages/cli/src/hooks/use-chat.ts, packages/cli/src/components/messages/bot-message.tsx, packages/cli/src/screens/session.tsx
Build-mode bash tool calls now pass through a session allowlist and approval prompt before execution, and assistant message rendering shows bash details and streaming state.
Ripgrep-backed search helper
packages/cli/package.json, packages/cli/src/lib/ripgrep.ts, packages/cli/src/lib/ripgrep.test.ts
The CLI adds ripgrep as a dependency, resolves a ripgrep binary, runs searches with stable output parsing, and covers the helper with tests.
Git inspection tools and plan-mode gating
packages/cli/package.json, packages/cli/src/lib/local-tools.ts, packages/cli/src/lib/local-tools.test.ts, packages/cli/src/lib/git-tools.test.ts
Plan mode now allows gitStatus and gitDiff, local tool execution handles those git reads plus ripgrep-backed grep, and tests cover PLAN-mode behavior and git diff/status output.
System prompt, permissions docs, and planning ignore rule
packages/server/src/system-prompt.ts, packages/server/src/system-prompt.test.ts, docs/agent-permissions.md, .gitignore
The server prompt and tests add Build-mode bash permission rules and PLAN/BUILD git tool guidance, the permissions doc describes the new behavior, and .gitignore excludes .planning/.

Sequence Diagram(s)

sequenceDiagram
  participant useChat as useChat.onToolCall
  participant requestBashApproval as requestBashApproval
  participant DialogProvider
  participant BashApprovalDialog
  participant executeLocalTool
  participant chatAddToolOutput as chat.addToolOutput
  participant User

  useChat->>requestBashApproval: ask for bash approval
  requestBashApproval->>DialogProvider: open BashApprovalDialog
  DialogProvider->>BashApprovalDialog: render dialog
  User->>BashApprovalDialog: choose approve / reject / allow-session
  BashApprovalDialog->>requestBashApproval: resolve verdict
  requestBashApproval-->>useChat: verdict

  alt reject
    useChat->>chatAddToolOutput: output-error
  else approve once or allow-session
    useChat->>executeLocalTool: run bash
    executeLocalTool-->>useChat: tool output
    useChat->>chatAddToolOutput: output or output-error
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • moyunzero/MoCode-TUI#11: Adds the build-mode bash approval gate that this PR extends with the approval dialog, session allowlist, and rejection handling.

Poem

A rabbit tapped the terminal bright,
For bash and git, I hopped just right.
I asked the dialog, then leapt on through,
With ripgrep trails and prompts anew. 🐰

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 62.50% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly matches the main change set: search, git tools, and bash TUI approval for Harness Phase 01.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch gsd/phase-1-search-git-foundation

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

🧹 Nitpick comments (2)
packages/shared/src/schemas.test.ts (1)

31-43: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Assert the exact PLAN/BUILD tool contract.

The PLAN test only checks inclusion, so it would still pass if bash or write tools accidentally leaked into PLAN mode.

✅ Proposed test tightening
   test("PLAN includes gitStatus and gitDiff", () => {
     const tools = getToolContracts(Mode.PLAN);
-    expect(Object.keys(tools)).toContain("gitStatus");
-    expect(Object.keys(tools)).toContain("gitDiff");
+    expect(Object.keys(tools).sort()).toEqual(
+      ["readFile", "listDirectory", "glob", "grep", "gitStatus", "gitDiff"].sort(),
+    );
   });
 
   test("BUILD includes git tools plus write/bash", () => {
     const tools = getToolContracts(Mode.BUILD);
-    expect(Object.keys(tools)).toContain("gitStatus");
-    expect(Object.keys(tools)).toContain("gitDiff");
-    expect(Object.keys(tools)).toContain("bash");
+    expect(Object.keys(tools).sort()).toEqual(
+      [
+        "readFile",
+        "listDirectory",
+        "glob",
+        "grep",
+        "gitStatus",
+        "gitDiff",
+        "writeFile",
+        "editFile",
+        "bash",
+      ].sort(),
+    );
   });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/shared/src/schemas.test.ts` around lines 31 - 43, The
getToolContracts tests are too permissive for Mode.PLAN because they only assert
inclusion and would miss accidental tool leaks. Tighten the assertions in
schemas.test.ts around getToolContracts so the PLAN case verifies the exact
returned contract only contains the expected git tools and excludes write/bash
tools, while the BUILD case still confirms the full expected set; use
getToolContracts and Mode as the anchor points.
packages/cli/src/providers/dialog/index.tsx (1)

33-38: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Move onClose out of the React state updater.

setCurrentDialog((prev) => { ... }) is doing imperative work inside a state updater. React can replay updater functions, so this callback contract is not guaranteed to fire exactly once. Read the current dialog first, invoke onClose, then clear state.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/cli/src/providers/dialog/index.tsx` around lines 33 - 38, The close
callback in the dialog provider is performing the onClose side effect inside the
setCurrentDialog updater, which can be replayed by React. Update the close
useCallback in the dialog context so it first reads the current dialog from
state, calls currentDialog.onClose if present, and then clears the dialog state
separately; keep the logic out of the state updater and preserve the existing
close behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/agent-permissions.md`:
- Line 7: The docs reference the wrong enforcement layer for blocked bash
commands. Update the text in agent-permissions.md to point to bash-approval.ts,
since requiresApproval(...) is only called from use-chat.ts and the actual
blocklist matching lives in packages/cli/src/lib/bash-approval.ts; use that
symbol to guide readers to the correct implementation location.

In `@packages/cli/src/hooks/use-chat.ts`:
- Around line 135-168: Move the bash input parsing for `toolCall.toolName ===
"bash"` inside the same `try/catch` that wraps `executeLocalTool` in
`use-chat.ts`, so malformed input from `toolInputSchemas.bash.parse` is also
converted into a terminal `output-error`. Keep the approval logic
(`requiresApproval`, `requestBashApproval`, `rememberSessionAllow`) intact, but
ensure any parse failure or other bash setup error is caught and passed to
`chat.addToolOutput` with `state: "output-error"` and `errorText` instead of
escaping before the catch.
- Line 126: The tool-call mode lookup in use-chat should not default from the
last message blindly, because the latest assistant/tool-continuation message may
have no metadata and can incorrectly fall back to BUILD. Update the mode
selection logic around the message metadata lookup so it scans backward for the
most recent metadata-bearing message and uses that message’s mode instead of
chat.messages.at(-1), ensuring executeLocalTool still respects PLAN-mode
read-only behavior.

In `@packages/cli/src/lib/bash-approval-ui.ts`:
- Around line 31-50: The requestBashApproval flow can hang because
DialogProvider.open() replaces the current dialog without settling the existing
one, so the promise in bash-approval-ui.ts never resolves when a reentrant
dialog opens. Update DialogProvider.open() to close or otherwise settle any
existing dialog before assigning the new currentDialog, and make sure the
existing dialog’s onClose path is triggered so requestBashApproval is not left
pending.

In `@packages/cli/src/lib/bash-approval.ts`:
- Around line 20-21: Expand the approval blocklist in bash-approval.ts so
destructive commands are caught in more common forms: update the rm pattern used
in the approval checks to match uppercase recursive flags like rm -R and mixed
forms like rm -Rf, and update the git push pattern to also catch refspec force
syntax such as git push origin +main in the same validation logic. Keep the
changes localized to the existing regexes near the rm and git push rules so the
Build-mode dialog still blocks these variants.

In `@packages/cli/src/lib/local-tools.ts`:
- Around line 141-151: The gitStatus result in local-tools is undercounting
dirty worktree entries because it only adds modified and deleted files to
unstaged, so other non-clean states can be missed. Update the git status mapping
to include all dirty worktree buckets from the git status object when computing
unstaged, and adjust the summary formatting in gitStatus so it reflects the full
dirty state rather than only staged/modified/untracked counts.

In `@packages/cli/src/lib/ripgrep.ts`:
- Around line 44-46: The argv built in ripgrep’s argument assembly is not stable
enough for the output parser used by local-tools. In the ripgrep helper that
constructs args for `rg`, make sure the search always produces
`file:line:content` and cannot treat the search term as an option: add the
appropriate flag to always include filenames and insert the end-of-options
separator before `pattern` and `resolved`. This should be fixed in the
argument-building logic in the ripgrep utility so
`packages/cli/src/lib/local-tools.ts` receives consistent matches.

In `@packages/shared/src/schemas.ts`:
- Around line 60-65: The gitDiff schema’s ref field is currently free-form, so
option-like values starting with a dash can be forwarded to git and treated as
flags. Harden the validation in the schema definition for gitDiff.ref by
rejecting leading-dash inputs (or otherwise constraining the accepted ref shape)
before they reach the git call site, and keep the behavior aligned with the
existing staged/compare description.

---

Nitpick comments:
In `@packages/cli/src/providers/dialog/index.tsx`:
- Around line 33-38: The close callback in the dialog provider is performing the
onClose side effect inside the setCurrentDialog updater, which can be replayed
by React. Update the close useCallback in the dialog context so it first reads
the current dialog from state, calls currentDialog.onClose if present, and then
clears the dialog state separately; keep the logic out of the state updater and
preserve the existing close behavior.

In `@packages/shared/src/schemas.test.ts`:
- Around line 31-43: The getToolContracts tests are too permissive for Mode.PLAN
because they only assert inclusion and would miss accidental tool leaks. Tighten
the assertions in schemas.test.ts around getToolContracts so the PLAN case
verifies the exact returned contract only contains the expected git tools and
excludes write/bash tools, while the BUILD case still confirms the full expected
set; use getToolContracts and Mode as the anchor points.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 8330b620-3dc9-44dc-b05a-8ef41ddc5889

📥 Commits

Reviewing files that changed from the base of the PR and between d864742 and 8f7635a.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (24)
  • .gitignore
  • docs/agent-permissions.md
  • packages/cli/package.json
  • packages/cli/src/components/dialogs/bash-approval-dialog.tsx
  • packages/cli/src/components/dialogs/index.tsx
  • packages/cli/src/components/messages/bot-message.tsx
  • packages/cli/src/hooks/use-chat.ts
  • packages/cli/src/lib/bash-approval-ui.ts
  • packages/cli/src/lib/bash-approval.test.ts
  • packages/cli/src/lib/bash-approval.ts
  • packages/cli/src/lib/dialog-action-nav.test.ts
  • packages/cli/src/lib/dialog-action-nav.ts
  • packages/cli/src/lib/git-tools.test.ts
  • packages/cli/src/lib/local-tools.test.ts
  • packages/cli/src/lib/local-tools.ts
  • packages/cli/src/lib/ripgrep.test.ts
  • packages/cli/src/lib/ripgrep.ts
  • packages/cli/src/providers/dialog/index.tsx
  • packages/cli/src/providers/dialog/types.ts
  • packages/cli/src/screens/session.tsx
  • packages/server/src/system-prompt.test.ts
  • packages/server/src/system-prompt.ts
  • packages/shared/src/schemas.test.ts
  • packages/shared/src/schemas.ts

Comment thread docs/agent-permissions.md Outdated
Comment thread packages/cli/src/hooks/use-chat.ts Outdated
Comment thread packages/cli/src/hooks/use-chat.ts Outdated
Comment on lines +31 to +50
dialog.open({
title: "Approve dangerous command",
// Fires on Esc, backdrop click, or dialog.close() — maps to reject unless already settled.
onClose: () => settle("reject"),
children: createElement(BashApprovalDialog, {
command,
onApproveOnce: () => {
settle("approve-once");
dialog.close();
},
onReject: () => {
settle("reject");
dialog.close();
},
onAllowSession: () => {
settle("allow-session");
dialog.close();
},
}),
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

requestBashApproval can hang if another dialog replaces it.

This promise only settles via the dialog callbacks/onClose, but DialogProvider.open() currently overwrites currentDialog directly. Any reentrant dialog.open() while this modal is pending drops the approval UI without settling the promise, so use-chat can block forever waiting for a verdict. Make open() close or settle the existing dialog before replacement.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/cli/src/lib/bash-approval-ui.ts` around lines 31 - 50, The
requestBashApproval flow can hang because DialogProvider.open() replaces the
current dialog without settling the existing one, so the promise in
bash-approval-ui.ts never resolves when a reentrant dialog opens. Update
DialogProvider.open() to close or otherwise settle any existing dialog before
assigning the new currentDialog, and make sure the existing dialog’s onClose
path is triggered so requestBashApproval is not left pending.

Comment thread packages/cli/src/lib/bash-approval.ts Outdated
Comment thread packages/cli/src/lib/local-tools.ts Outdated
Comment thread packages/cli/src/lib/ripgrep.ts Outdated
Comment thread packages/shared/src/schemas.ts
@railway-app

railway-app Bot commented Jun 27, 2026

Copy link
Copy Markdown

🚅 Deployed to the MoCode-TUI-pr-13 environment in lavish-courage

Service Status Web Updated (UTC)
@mocode/server ✅ Success (View Logs) Web Jun 27, 2026 at 3:08 am

Harden tool-call mode lookup, ripgrep argv parsing, bash blocklist coverage,
gitDiff.ref validation, dialog lifecycle, and gitStatus dirty counts per PR #13 CR.

Co-authored-by: Cursor <cursoragent@cursor.com>
@moyunzero moyunzero merged commit a305999 into master Jun 27, 2026
2 checks passed
@railway-app railway-app Bot temporarily deployed to lavish-courage / MoCode-TUI-pr-13 June 27, 2026 03:33 Destroyed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant